package com.wiringweb.servlet;

import com.gooddies.utils.Pair;
import com.wiringweb.exceptions.WiringWebException;
import com.wiringweb.http.HttpMethod;
import com.wiringweb.parameterprocessor.ParameterProcessorInterface;
import com.wiringweb.parameterprocessor.ParameterProcessorSettings;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author Dmitry
 */
public class UrlsContainer {
    private final List<UrlBind> emptyPrefixBinding = new ArrayList<UrlBind>();
    private final Map<String, List<UrlBind>> controllerPrefixesToListMap = new HashMap<String, List<UrlBind>>(100);

    public void addUrl(ParameterProcessorSettings psettings, String prefix, Pattern regexPattern, BaseWiringWebController controller, Method method, HttpMethod httpMethod) {
        UrlBind bind = new UrlBind();
        bind.setMatcher(regexPattern);
        bind.setHttpMethod(httpMethod);
        bind.setMethod(method);
        bind.setController(controller);
        ParameterProcessorInterface[] processors = processMethodParameters(psettings, method, controller);
        bind.setParameterProcessors(processors);
        if (prefix == null || prefix.isEmpty()) {
            emptyPrefixBinding.add(bind);
        } else {
            List<UrlBind> list = controllerPrefixesToListMap.get(prefix);
            if (list == null) {
                list = new ArrayList<UrlBind>();
                controllerPrefixesToListMap.put(prefix, list);
            }

            list.add(bind);
        }
    }

    private ParameterProcessorInterface[] processMethodParameters(ParameterProcessorSettings psettings, Method method, BaseWiringWebController controller) {
        Annotation[][] annotations = method.getParameterAnnotations();
        Class[] parameters = method.getParameterTypes();
        ParameterProcessorInterface[] result = new ParameterProcessorInterface[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            ParameterProcessorInterface pp;
            Annotation annotation=null;
            if (annotations[i] != null && annotations[i].length > 0) {
                annotation=annotations[i][0];
                pp = psettings.getParameterProcessor(annotation.annotationType());
            } else {
                pp = psettings.getParameterProcessor(parameters[i]);
            }
            if (pp == null) {
                String message = String.format("Parameter %s in method %s of controller cannot be processed. May be you forgot about PathVariable annotation?",
                        parameters[i].getName(), method.getName(),
                        controller.getClass().getName());
                throw new WiringWebException(message);
            }
            
            ParameterProcessorInterface ppimpl = pp.construct(parameters[i],annotation);
            result[i] = ppimpl;
        }

        return result;
    }

    protected static Pair<String, String> getServerPrefix(String url) {
        int firstIndex = url.indexOf('/');
        int secondIndex;
        if (firstIndex == 0) {
            firstIndex = 1;
            secondIndex = url.indexOf('/', 1);
        } else if (firstIndex > 0) {
            secondIndex = firstIndex;
            firstIndex = 0;
        } else {
            return null;
        }
        if (firstIndex == -1 || secondIndex == -1) {
            return null;
        }

        String serverPrefix = url.substring(firstIndex, secondIndex);
        String otherPart = url.substring(secondIndex);
        return new Pair<String, String>(serverPrefix, otherPart);
    }

    public UrlBindWithUrl resolveUrl(String url) {
        Pair<String, String> prefix = getServerPrefix(url);
        if (prefix == null) {
            UrlBind bind = tryToSearchUrl(emptyPrefixBinding, url);
            if (bind == null) {
                return null;
            }
            return new UrlBindWithUrl(bind, url);
        }

        List<UrlBind> list = controllerPrefixesToListMap.get(prefix.getFirst());
        if (list == null) {
            UrlBind bind = tryToSearchUrl(emptyPrefixBinding, url);
            if (bind == null) {
                return null;
            }
            return new UrlBindWithUrl(bind, url);
        } else {
            String strippedUrl = prefix.getSecond();
            UrlBind bind = tryToSearchUrl(list, strippedUrl);
            if (bind == null) {
                return null;
            }
            return new UrlBindWithUrl(bind, strippedUrl);
        }
    }

    public UrlBind tryToSearchUrl(List<UrlBind> bindArray, String url) {
        if (bindArray == null) {
            return null;
        }

        for (UrlBind urlBind : bindArray) {
            if (urlBind.getMatcher().matcher(url).matches()) {
                return urlBind;
            }
        }

        return null;
    }
}
